﻿//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//********************************************************* 

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using Microsoft.Office.Core;
using Microsoft.Office.Interop.Word;
using Microsoft.TridentWordAddIn.Common;
using Microsoft.TridentWordAddIn.ResourceHelper;
using CustomTaskPane = Microsoft.Office.Tools.CustomTaskPane;
using Word = Microsoft.Office.Interop.Word;
using System.Linq;


namespace Microsoft.TridentWordAddIn.AddIn
{
    /// <summary>
    /// The class is the actual add-in being loaded by Word 2007. It also the gateway
    /// for a IScentificPackage implementation to access functions in Word by abstraction.
    /// </summary>
    public partial class ThisAddIn : IWindowContainer, IDisposable, IThisAddInPackageHost
    {
        #region Fields

        private const string CustomXMLNamespace = "Microsoft.TridentWordAddIn.AddIn";
        private static int docCounter = 1;
        private bool disposed;
        private bool windowSelectionChanging; // this is used to keep multiple window selection change events from firing inside each other
        private bool isPictureInCurrentSelection;
        private string previousSelectedPicture = string.Empty;
        private IRibbonExtensibility ribbon;
        private TaskPaneManager taskPaneManager;
        private IDocumentStorage docStorage;
        private Dictionary<object, int> documentKeyDictionary = new Dictionary<object, int>();
        private IList<Guid> currentGuids;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the document count.
        /// </summary>
        /// <value>The document count.</value>
        public int DocumentsCount
        {
            get
            {
                return this.Application.Documents.Count;
            }
        }

        /// <summary>
        /// Gets the active document key in the Word application.
        /// </summary>
        /// <value>The active document key.</value>
        public int ActiveDocumentKey
        {
            get
            {
                Word.Document curDoc = this.Application.ActiveDocument;
                Guid? uniqueKeyNullable = curDoc.GuidFromCustomXmlPart(true);
                if (uniqueKeyNullable == null)
                {
                    throw new Exception("Doc key was null even though we asked for one to be generated!");
                }

                Guid uniqueKey = uniqueKeyNullable.Value;
                if (this.documentKeyDictionary.ContainsKey(uniqueKey))
                {
                    return this.documentKeyDictionary[uniqueKey];
                }
                else
                {
                    this.documentKeyDictionary.Add(uniqueKey, docCounter);
                    docCounter++;
                    return docCounter - 1;
                }
            }
        }

        /// <summary>
        /// Determines whether [is document protected] [the specified document key].
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <returns>
        /// 	<c>true</c> if [is document protected] [the specified document key]; otherwise, <c>false</c>.
        /// </returns>
        private bool IsCurrentDocumentProtected
        {
            get
            {
                try
                {
                    return this.Application.ActiveDocument.ProtectionType != WdProtectionType.wdNoProtection;
                }
                catch (COMException)
                {
                    return false;
                }
            }
        }

        /// <summary>
        /// Gets the document range.
        /// </summary>
        /// <value>The document range.</value>
        public PictureRange DocumentRange
        {
            get
            {
                Range rng = this.Application.Selection.Range;
                PictureRange range = new PictureRange();
                range.Start = rng.Start;
                range.End = rng.End;
                return range;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is document saved.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is document saved; otherwise, <c>false</c>.
        /// </value>
        public bool IsDocumentDirty
        {
            get { return !this.Application.ActiveDocument.Saved; }
            set { this.Application.ActiveDocument.Saved = !value; }
        }

        #endregion

        #region Display Ribbon

        /// <summary>
        /// Returns an object that extends a feature in the 2007 Microsoft Office system.
        /// </summary>
        /// <param name="serviceGuid">A <see cref="T:System.Guid"></see> that identifies an extensibility interface that is supported by applications in the 2007 Microsoft Office system.</param>
        /// <returns>
        /// An object that implements the extensibility interface that is identified by serviceGuid.
        /// </returns>
        protected override object RequestService(Guid serviceGuid)
        {
            if (serviceGuid == typeof(IRibbonExtensibility).GUID)
            {
                Debug.WriteLine("RequestService for Ribbon");
                this.ribbon = this.ribbon ?? this.GetRibbonFromPackage();
                return this.ribbon;
            }

            return base.RequestService(serviceGuid);
        }

        /// <summary>
        /// Gets the ribbon from package.
        /// </summary>
        /// <returns>IRibbonExtensibility</returns>
        private IRibbonExtensibility GetRibbonFromPackage()
        {
            IRibbonExtensibility tempRibbon = null;
            ITridentPackage package = this.GetPackage();
            if (package != null)
            {
                tempRibbon = package.GetRibbon() as IRibbonExtensibility;
            }
            else
            {
                Trace.WriteLine("GetRibbonFromPackage: package was null", TraceLevel.Error.ToString());
            }

            return tempRibbon;
        }

        /// <summary>
        /// Instantiates and returns an implementation of a package from configuration
        /// </summary>
        /// <returns>the new instance of a IScientificPackage</returns>
        private ITridentPackage GetPackage()
        {
            ITridentPackage package = null;
            Assembly packageAssembly = null;

            try
            {
                string assembly = Properties.Settings.Default.PlugInAssembly;
                string packageType = Properties.Settings.Default.PlugInType;
                string path = Properties.Settings.Default.PlugInAssemblyPath;
                bool assemblyLoaded = false;
                try
                {
                    packageAssembly = Assembly.Load(assembly);
                    package = packageAssembly.CreateInstance(packageType) as ITridentPackage;
                    assemblyLoaded = true;
                }
                catch (Exception ex)
                {
                    Trace.WriteLine("Unable to assembly " + assembly + ". Exception: " + ex, TraceLevel.Warning.ToString());
                }

                if (!assemblyLoaded)
                {
                    if (string.IsNullOrEmpty(path))
                    {
                        Trace.WriteLine("setting PlugInAssemblyPath was null or empty", TraceLevel.Error.ToString());
                    }
                    //// build path to assembly directory 
                    if (!Path.IsPathRooted(path))
                    {
                        string currentDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
                        //// strip "file:/" from the front of the currentDir
                        currentDir = currentDir.Substring(6);
                        path = Path.Combine(currentDir, path);
                    }

                    if (path.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(System.IO.File.Exists(path), "Assembly path doesn't exist");
                        //// it's a single dll
                        packageAssembly = Assembly.LoadFrom(path);
                        packageAssembly.GetTypes();
                        package = packageAssembly.CreateInstance(packageType) as ITridentPackage;
                    }
                    else
                    {
                        Debug.Assert(System.IO.Directory.Exists(path), "Assembly path doesn't exist");
                        //// it's a directory
                        string[] files = System.IO.Directory.GetFiles(path, "*.dll");
                        foreach (string file in files)
                        {
                            Assembly asm = Assembly.LoadFrom(file);
                            //// look for the class we are supposed to create
                            foreach (Type t in asm.GetTypes())
                            {
                                if (t.ToString() == packageType)
                                {
                                    package = asm.CreateInstance(packageType) as ITridentPackage;
                                }
                            }
                        }
                    }
                }

                if (package != null)
                {
                    // bind interfaces to package
                    this.taskPaneManager = new TaskPaneManager(this);
                    package.SetTaskPaneManager(this.taskPaneManager);
                    package.SetPackageHost(this);
                    this.docStorage = new CustomXmlPartManager(this.Application);
                    this.docStorage.PackageHost = this;

                    package.SetDocumentStorage(this.docStorage);
                }
                else
                {
                    Debug.Assert(false, String.Format("Can't create package {0} from any of the .dlls that were loaded from {1}", packageType, path));
                    Trace.WriteLine("Unable to create IScientificPackage from assembly specified by " + path, TraceLevel.Error.ToString());
                }
            }
            catch (Exception e)
            {
                Trace.WriteLine("Exception in GetPackage" + e, TraceLevel.Error.ToString());
            }

            return package;
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Handles the Startup event of the ThisAddIn control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
            Debug.WriteLine("ThisAddIn_Startup");

            this.Application.WindowSelectionChange += new ApplicationEvents4_WindowSelectionChangeEventHandler(this.Application_WindowSelectionChange);
            this.Application.DocumentBeforeSave += new ApplicationEvents4_DocumentBeforeSaveEventHandler(this.Application_DocumentBeforeSave);
            this.Application.DocumentBeforeClose += new ApplicationEvents4_DocumentBeforeCloseEventHandler(this.Application_DocumentBeforeClose);
            this.Application.DocumentOpen += new ApplicationEvents4_DocumentOpenEventHandler(this.Application_DocumentOpen);
            this.Application.WindowActivate += new ApplicationEvents4_WindowActivateEventHandler(this.Application_WindowActivate);
        }

        /// <summary>
        /// Application_s the window activate.
        /// </summary>
        /// <param name="document">The document.</param>
        /// <param name="window">The window.</param>
        private void Application_WindowActivate(Document document, Window window)
        {
            this.OnDocumentActivate();
        }

        /// <summary>
        /// Application_s the document before close.
        /// </summary>
        /// <param name="document">The document.</param>
        /// <param name="cancel">if set to <c>true</c> [cancel].</param>
        private void Application_DocumentBeforeClose(Document document, ref bool cancel)
        {
            this.OnDocumentClose(ref cancel);
        }

        /// <summary>
        /// Application_s the document open.
        /// </summary>
        /// <param name="document">The document.</param>
        private void Application_DocumentOpen(Document document)
        {
            this.OnDocumentOpen();
        }

        /// <summary>
        /// Handles the Shutdown event of the ThisAddIn control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
            if (this.OnAddInShutdown != null)
            {
                this.OnAddInShutdown(this, EventArgs.Empty);
            }

            DirectoryHelper.ClearTemporaryDirectory();
            Debug.WriteLine("ThisAddIn_Shutdown");
        }

        /// <summary>
        /// Application_s the window selection change.
        /// </summary>
        /// <param name="selection">The selection.</param>
        private void Application_WindowSelectionChange(Selection selection)
        {
            if (this.windowSelectionChanging)
            {
                return;
            }

            try
            {
                this.windowSelectionChanging = true;
                DocumentPicture documentPicture = this.GetSelectedPicture();
                if (documentPicture != null && !this.previousSelectedPicture.CompareIgnoreCase(documentPicture.Key))
                {
                    this.isPictureInCurrentSelection = true;
                    this.previousSelectedPicture = documentPicture.Key;
                    this.OnPictureSelected(documentPicture);
                }
                else if (documentPicture == null && this.isPictureInCurrentSelection)
                {
                    this.isPictureInCurrentSelection = false;
                    this.previousSelectedPicture = string.Empty;
                    this.OnPictureSelected(null);
                }

                this.isPictureInCurrentSelection = documentPicture != null;
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Excpetion: " + ex, TraceLevel.Error.ToString());
            }
            finally
            {
                this.windowSelectionChanging = false;
            }
        }

        /// <summary>
        /// Application_s the document before save.
        /// </summary>
        /// <param name="document">The document.</param>
        /// <param name="saveAsUI">if set to <c>true</c> [save as UI].</param>
        /// <param name="cancel">if set to <c>true</c> [cancel].</param>
        private void Application_DocumentBeforeSave(Document document, ref bool saveAsUI, ref bool cancel)
        {
            this.OnDocumentSave(ref cancel);
        }

        /// <summary>
        /// Called when [document save].
        /// </summary>
        /// <param name="cancel">if set to <c>true</c> [cancel].</param>
        private void OnDocumentSave(ref bool cancel)
        {
            if (this.DocumentSave != null)
            {
                DocumentSaveEventArgs saveEventArgs = new DocumentSaveEventArgs();
                this.DocumentSave(this, saveEventArgs);
                cancel = saveEventArgs.CancelSaveDialog;
            }
        }

        /// <summary>
        /// Called when [document activate].
        /// </summary>
        private void OnDocumentActivate()
        {
            if (this.DocumentActivated != null)
            {
                this.DocumentActivated(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Called when [picture selected].
        /// </summary>
        /// <param name="documentPicture">The document picture.</param>
        private void OnPictureSelected(DocumentPicture documentPicture)
        {
            if (this.OnPictureSelectionChanged != null)
            {
                PictureSelectionChangedEventArgs pictureChangedEventArgs =
                    new PictureSelectionChangedEventArgs(this.ActiveDocumentKey, documentPicture);
                this.OnPictureSelectionChanged(this, pictureChangedEventArgs);
            }
        }

        /// <summary>
        /// Called when [document close].
        /// </summary>
        /// <param name="cancel">if set to <c>true</c> [cancel].</param>
        private void OnDocumentClose(ref bool cancel)
        {
            if (this.DocumentClose != null)
            {
                CancelCloseEventArgs args = new CancelCloseEventArgs();
                this.DocumentClose(this, args);
                cancel = args.Cancel;
            }
        }

        /// <summary>
        /// Called when [document open].
        /// </summary>
        private void OnDocumentOpen()
        {
            // delete document key so we regenerate later
            Word.Document currentDocument = this.Application.ActiveDocument;
            this.ClearCustomGuidFromXmlPart(currentDocument);

            if (this.DocumentOpen != null)
            {
                this.DocumentOpen(this, EventArgs.Empty);
            }
        }

        #endregion

        #region VSTO generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(ThisAddIn_Startup);
            this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
        }

        #endregion

        #region IWindowContainer Members

        /// <summary>
        /// Adds a new task pane to the current active window in Word 2007
        /// </summary>
        /// <param name="userControl">The user control to be displayed in the CustomTaskPane</param>
        /// <param name="taskPaneTitle">The task pane title.</param>
        /// <returns>A wrapper for the new CustomTaskPane</returns>
        public CustomTaskPane AddTaskPane(UserControl userControl, string taskPaneTitle)
        {
            Window window = this.Application.ActiveWindow;
            if (window != null)
            {
                return this.CustomTaskPanes.Add(userControl, taskPaneTitle, window);
            }

            return null;
        }

        /// <summary>
        /// Removes the task panes for the active document/window.
        /// </summary>
        public void RemoveTaskPanes()
        {
            List<CustomTaskPane> toRemove = new List<CustomTaskPane>();
            try
            {
                Window window = this.Application.ActiveWindow;
                if (window != null)
                {
                    foreach (CustomTaskPane customTaskPane in this.CustomTaskPanes)
                    {
                        if (customTaskPane.Window == window)
                        {
                            toRemove.Add(customTaskPane);
                        }
                    }

                    foreach (CustomTaskPane customTaskPane in toRemove)
                    {
                        this.RemoveTaskPane(customTaskPane);
                    }
                }
            }
            catch (COMException)
            {
            }
        }

        /// <summary>
        /// Removes the task pane.
        /// </summary>
        /// <param name="title">The title.</param>
        public void RemoveTaskPane(string title)
        {
            try
            {
                Window window = this.Application.ActiveWindow;
                if (window != null)
                {
                    foreach (CustomTaskPane customTaskPane in this.CustomTaskPanes)
                    {
                        if (customTaskPane.Window == window && customTaskPane.Title == title)
                        {
                            this.RemoveTaskPane(customTaskPane);
                            break;
                        }
                    }
                }
            }
            catch (COMException)
            {
            }
        }

        /// <summary>
        /// Removes the task pane.
        /// </summary>
        /// <param name="customTaskPane">The custom task pane.</param>
        private void RemoveTaskPane(CustomTaskPane customTaskPane)
        {
            UserControl userControl = customTaskPane.Control;
            if (userControl != null)
            {
                if (userControl.InvokeRequired)
                {
                    userControl.Invoke(new DiposeDelegate(this.DisposeUserControl), new object[] { userControl });
                }
            }

            bool wasPaneRemoved = this.CustomTaskPanes.Remove(customTaskPane);
            if (!wasPaneRemoved)
            {
                Trace.WriteLine("Unable to remove Custom Task Pane : " + customTaskPane.Title, TraceLevel.Warning.ToString());
            }
            else
            {
                Trace.WriteLine("Custom Task Pane removed", TraceLevel.Info.ToString());
            }
        }

        /// <summary>
        /// Delegate for Disposing Resources
        /// </summary>
        private delegate void DiposeDelegate(UserControl userControl);

        /// <summary>
        /// Disposes the user control.
        /// </summary>
        /// <param name="userControl">The user control.</param>
        private void DisposeUserControl(UserControl userControl)
        {
            if (userControl != null && !userControl.IsDisposed)
            {
                userControl.Dispose();
            }
        }

        /// <summary>
        /// Gets the task pane.
        /// </summary>
        /// <param name="title">The title.</param>
        /// <param name="documentKey">The document key.</param>
        /// <returns>CustomTaskPane</returns>
        public CustomTaskPane GetTaskPane(string title, int documentKey)
        {
            CustomTaskPane result = null;
            Document document = this.GetDocumentByDocumentKey(documentKey);

            if (document != null)
            {
                Window window = document.ActiveWindow;

                if (window != null)
                {
                    foreach (CustomTaskPane customTaskPane in this.CustomTaskPanes)
                    {
                        if (customTaskPane.Window == window && customTaskPane.Title == title)
                        {
                            result = customTaskPane;
                            break;
                        }
                    }
                }
            }

            return result;
        }

        #endregion

        #region IPackageHost Members

        /// <summary>
        /// Event raised before user saves the document
        /// </summary>
        public event EventHandler<EventArgs> OnAddInShutdown;

        /// <summary>
        /// Event raised before user saves the document
        /// </summary>
        public event EventHandler<DocumentSaveEventArgs> DocumentSave;

        /// <summary>
        /// Occurs when [document activated].
        /// </summary>
        public event EventHandler DocumentActivated;

        /// <summary>
        /// Event raised before user closes the document
        /// </summary>
        public event EventHandler<CancelCloseEventArgs> DocumentClose;

        /// <summary>
        /// Event raised after user opens a document
        /// </summary>
        public event EventHandler DocumentOpen;

        /// <summary>
        /// Event raised when an Image is selected by the user
        /// </summary>
        public event EventHandler<PictureSelectionChangedEventArgs> OnPictureSelectionChanged;

        /// <summary>
        /// Determines whether [is document protected] [the specified document key].
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <returns>
        /// 	<c>true</c> if [is document protected] [the specified document key]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsDocumentProtected(int documentKey)
        {
            try
            {
                Document document = this.GetDocumentByDocumentKey(documentKey);
                return document.ProtectionType != WdProtectionType.wdNoProtection;
            }
            catch (COMException)
            {
                return false;
            }
        }

        /// <summary>
        /// Insert an image at the current cursor location in the host
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="range">The document range where image to be inserted.</param>
        /// <param name="image">The image to be inserted.</param>
        /// <returns>PictureRange</returns>
        public PictureRange InsertPicture(int documentKey, PictureRange range, Image image)
        {
            if (image == null)
            {
                throw new ArgumentNullException("image");
            }

            object endOfDoc = WdUnits.wdCharacter;
            object moveToEnd = WdMovementType.wdMove;
            object start = range.Start;
            object end = range.End;

            // insert a space before the image
            Document document = this.GetDocumentByDocumentKey(documentKey);
            Range rng = document.Range(ref start, ref end);
            rng.Text = " ";
            try
            {
                Clipboard.SetImage(image);
                rng.Paste();
                ClearClipBoard();
                rng.Select();

                moveToEnd = WdMovementType.wdExtend;
                rng.EndOf(ref endOfDoc, ref moveToEnd);
                rng.Select();
                Thread.Sleep(200); // needed for app not to hang
                rng.InsertBefore(" ");

                return new PictureRange(rng.Start + 1, rng.End);
            }
            catch (ExternalException ex)
            {
                Trace.WriteLine("External exception during insert image:" + ex.StackTrace);
                throw new InvalidOperationException("Failure during clipboard operations.", ex);
            }
        }

        /// <summary>
        /// Gets the selected picture.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="range">The range.</param>
        /// <param name="isTridentLink">if set to <c>true</c> [is trident link].</param>
        /// <returns>DocumentPicture</returns>
        public DocumentPicture GetSelectedPicture(int documentKey, PictureRange range, bool isTridentLink)
        {
            Document document = this.GetDocumentByDocumentKey(documentKey);

            if (document.InlineShapes.Count > 0)
            {
                foreach (InlineShape inlineShape in document.InlineShapes)
                {
                    if (inlineShape.Range.Start == range.Start &&
                        inlineShape.Range.End == range.End)
                    {
                        return CreateDocoumentPicture(inlineShape, isTridentLink);
                    }
                }

                foreach (InlineShape inlineShape in document.InlineShapes)
                {
                    if (inlineShape.Range.End == range.End)
                    {
                        return CreateDocoumentPicture(inlineShape, isTridentLink);
                    }
                }

                foreach (InlineShape inlineShape in document.InlineShapes)
                {
                    if (inlineShape.Range.Start == range.Start)
                    {
                        return CreateDocoumentPicture(inlineShape, isTridentLink);
                    }
                }

                return null;
            }
            else if (document.Shapes.Count > 0)
            {
                foreach (Word.Shape shape in document.Shapes)
                {
                    if (string.IsNullOrEmpty(shape.AlternativeText))
                    {
                        return CreateDocoumentPicture(shape, isTridentLink);
                    }
                }

                return null;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Clears the custom GUID from XML part.
        /// </summary>
        /// <param name="curDoc">The cur doc.</param>
        private void ClearCustomGuidFromXmlPart(Document curDoc)
        {
            // get the guids of other open docs 
            this.currentGuids = new List<Guid>();
            foreach (Document d in this.Application.Documents)
            {
                if (d != this.Application.ActiveDocument)
                {
                    Guid? docGuid = d.GuidFromCustomXmlPart(false);
                    if (docGuid != null)
                    {
                        this.currentGuids.Add(docGuid.Value);
                    }
                }
            }

            Guid? myGuid = this.Application.ActiveDocument.GuidFromCustomXmlPart(false);
            //// if my guid conflicts with a known document, delete mine so it gets created later
            if (myGuid != null && this.currentGuids.Contains(myGuid.Value))
            {
                CustomXMLParts namespaceParts = curDoc.CustomXMLParts.SelectByNamespace(CustomXMLNamespace);
                if (namespaceParts.Count > 0)
                {
                    foreach (CustomXMLPart part in namespaceParts)
                    {
                        part.Delete();
                    }
                }
            }
        }

        /// <summary>
        /// Gets the image from inline shape.
        /// </summary>
        /// <param name="inlineShape">The inline shape.</param>
        /// <returns>Image</returns>
        private Image GetImageFromInlineShape(InlineShape inlineShape)
        {
            bool prevWindowSelectionStatus = this.windowSelectionChanging;
            this.windowSelectionChanging = true;
            Image img = null;
            try
            {
                if (!this.IsCurrentDocumentProtected)
                {
                    try
                    {
                        inlineShape.Range.CopyAsPicture();
                        if (Clipboard.ContainsImage())
                        {
                            img = Clipboard.GetImage();
                            ClearClipBoard();
                        }
                    }
                    catch (COMException)
                    {
                    }
                    catch (ExternalException ex)
                    {
                        Trace.WriteLine("Exception: " + ex, TraceLevel.Error.ToString());
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                if (this.windowSelectionChanging)
                {
                    this.windowSelectionChanging = prevWindowSelectionStatus;
                }
            }

            return img;
        }

        /// <summary>
        /// Clears the clip board.
        /// </summary>
        private static void ClearClipBoard()
        {
            try
            {
                Clipboard.Clear();
            }
            catch (ExternalException ex)
            {
                Trace.WriteLine("Exception: " + ex, TraceLevel.Error.ToString());
            }
        }

        /// <summary>
        /// Protects the document.
        /// </summary>
        /// <param name="password">The password.</param>
        /// <returns>bool</returns>
        public bool ProtectDocument(string password)
        {
            try
            {
                return this.Application.ProtectDocument(password);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Unprotects the document.
        /// </summary>
        /// <param name="password">The password.</param>
        /// <returns>bool</returns>
        public bool UnprotectDocument(string password)
        {
            object passwordString = password;
            DocumentPicture picture = GetSelectedPicture();
            try
            {
                return this.Application.UnprotectDocument(passwordString);
            }
            catch (COMException ex)
            {
                if (ex.Message.Contains("password"))
                {
                    return false;
                }
                else if (ex.Message.Contains("The requested member of the collection does not exist.") && ex.ErrorCode.Equals(-2146822347))
                {
                    this.Application.ActiveDocument.Unprotect(ref passwordString);
                    return true;
                }

                throw;
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                if (picture != null)
                {
                    this.SelectPicture(this.ActiveDocumentKey, picture.Key);
                }
            }
        }

        /// <summary>
        /// Gets the selected image.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="pictureKey">The picture key.</param>
        /// <returns>Image</returns>
        public Image GetSelectedPicture(int documentKey, string pictureKey)
        {
            Document document = this.GetDocumentByDocumentKey(documentKey);

            DocumentPicture documentPicture;
            foreach (InlineShape inlineShape in document.InlineShapes)
            {
                documentPicture = CreateDocoumentPicture(inlineShape, false);
                if (documentPicture != null)
                {
                    if (documentPicture.Key.CompareIgnoreCase(pictureKey))
                    {
                        return this.GetImageFromInlineShape(inlineShape);
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Sets the selected image.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="pictureKey">The picture key.</param>
        public void SelectPicture(int documentKey, string pictureKey)
        {
            Document document = this.GetDocumentByDocumentKey(documentKey);
            foreach (InlineShape inlineShape in document.InlineShapes)
            {
                if (CreateDocoumentPicture(inlineShape, false).Key == pictureKey)
                {
                    inlineShape.Select();
                    return;
                }
            }

            foreach (Word.Shape shape in document.Shapes)
            {
                if (CreateDocoumentPicture(shape, false).Key == pictureKey)
                {
                    object dummy = new object();
                    shape.Select(ref dummy);
                    return;
                }
            }
        }

        /// <summary>
        /// Gets all images.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <returns>ReadOnlyCollection of DocumentPicture</returns>
        public ReadOnlyCollection<DocumentPicture> GetAllPictures(int documentKey)
        {
            Document document = this.GetDocumentByDocumentKey(documentKey);
            List<DocumentPicture> images = new List<DocumentPicture>();
            foreach (InlineShape inlineShape in document.InlineShapes)
            {
                images.Add(CreateDocoumentPicture(inlineShape, false));
            }

            foreach (Word.Shape shape in document.Shapes)
            {
                images.Add(CreateDocoumentPicture(shape, false));
            }

            return new ReadOnlyCollection<DocumentPicture>(images);
        }

        /// <summary>
        /// Determines whether [is model key exists] [the specified key].
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>
        /// 	<c>true</c> if [is model key exists] [the specified key]; otherwise, <c>false</c>.
        /// </returns>
        public bool ModelExists(int key)
        {
            if (this.currentGuids != null)
            {
                IDictionaryEnumerator enumerator = this.documentKeyDictionary.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    if ((int)enumerator.Value == key)
                    {
                        return this.currentGuids.Contains(new Guid(enumerator.Key.ToString()));
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Gets the document using document key.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <returns>Document</returns>
        public Document GetDocumentByDocumentKey(int documentKey)
        {
            IDictionaryEnumerator ie = this.documentKeyDictionary.GetEnumerator();
            Guid documentGuid = new Guid();
            while (ie.MoveNext())
            {
                if (documentKey == (int)ie.Value)
                {
                    documentGuid = new Guid(ie.Key.ToString());
                    break;
                }
            }

            if (documentGuid != Guid.Empty)
            {
                foreach (Document document in this.Application.Documents)
                {
                    Guid? docGuid = document.GuidFromCustomXmlPart(false);
                    if (docGuid == documentGuid)
                    {
                        return document;
                    }
                }
            }

            return this.Application.ActiveDocument;
        }

        /// <summary>
        /// Gets the selected picture.
        /// </summary>
        /// <returns>DocumentPicture</returns>
        public DocumentPicture GetSelectedPicture()
        {
            Selection selection = this.Application.Selection;
            DocumentPicture documentPicture = null;
            if (selection.InlineShapes.Count > 0)
            {
                foreach (InlineShape inlineShape in selection.InlineShapes)
                {
                    documentPicture = CreateDocoumentPicture(inlineShape, false);
                    break;
                }

                return documentPicture;
            }

            if (selection.ShapeRange.Count > 0)
            {
                foreach (Word.Shape shape in selection.ShapeRange)
                {
                    documentPicture = CreateDocoumentPicture(shape, false);
                    break;
                }

                return documentPicture;
            }

            return documentPicture;
        }

        /// <summary>
        /// Adds the appendix entry.
        /// </summary>
        /// <param name="appendixResult">The appendix result.</param>
        public void AddAppendixEntry(AppendixResult appendixResult)
        {
            this.Application.MoveToEndOfDocument(false);
            this.Application.MoveSelectionToEnd();
            this.Application.NewLine();
            this.Application.MoveToEndOfDocument(false);
            string header = appendixResult.IsOrginal ? "Original run" : "Rerun";
            this.Application.InsertText(header + " of Trident Workflow Association '" + appendixResult.AssociationName + "'", 12, WdUnderline.wdUnderlineNone, true);
            this.Application.InsertText("Added at: " + DateTime.Now, 11, WdUnderline.wdUnderlineNone, false);
            this.Application.InsertText("Comment: " + appendixResult.Comment, 11, WdUnderline.wdUnderlineNone, false);
            this.Application.InsertText("Scheduled by: " + appendixResult.ScheduledBy, 11, WdUnderline.wdUnderlineNone, false);

            bool addInputdetails = appendixResult.RequiredInputParameters != null && appendixResult.RequiredInputParameters.Count != 0;
            Range inputTableRange = null;
            if (addInputdetails)
            {
                this.Application.InsertText("Workflow Required Inputs:", 11, WdUnderline.wdUnderlineSingle, false);
                inputTableRange = this.Application.Selection.Range;
            }

            bool addOutputdetails = appendixResult.RequiredOutputParameters != null && appendixResult.RequiredOutputParameters.Count != 0;
            Range outputTableRange = null;
            if (addOutputdetails)
            {
                this.Application.MoveToEndOfDocument(true);
                this.Application.InsertText("Workflow Required Outputs:", 11, WdUnderline.wdUnderlineSingle, false);
                outputTableRange = this.Application.Selection.Range;
            }

            this.Application.MoveToEndOfDocument(true);
            string message = string.Empty;
            switch (appendixResult.OutPutType)
            {
                case OutputType.Workflow:
                    message = "Inserted Workflow Outputs:";
                    break;
                case OutputType.DataProduct:
                    message = "Inserted Data Products:";
                    break;
            }

            this.Application.InsertText(message, 11, WdUnderline.wdUnderlineSingle, false);
            InsertAppendixResult(appendixResult);

            if (addInputdetails)
            {
                InsertParameterDetails(appendixResult.RequiredInputParameters, inputTableRange, true);
            }

            if (addOutputdetails)
            {
                InsertParameterDetails(appendixResult.RequiredOutputParameters, outputTableRange, false);
            }
        }

        /// <summary>
        /// Deletes the image.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="pictureKey">The picture key.</param>
        public void DeleteImage(int documentKey, string pictureKey)
        {
            Document document = this.GetDocumentByDocumentKey(documentKey);
            foreach (InlineShape inlineShape in document.InlineShapes)
            {
                if (CreateDocoumentPicture(inlineShape, false).Key == pictureKey)
                {
                    inlineShape.Delete();
                    return;
                }
            }

            foreach (Word.Shape shape in document.Shapes)
            {
                if (CreateDocoumentPicture(shape, false).Key == pictureKey)
                {
                    shape.Delete();
                    return;
                }
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Creates the docoument picture.
        /// </summary>
        /// <param name="inlineShape">The inline shape.</param>
        /// <returns>DocumentPicture</returns>
        private static DocumentPicture CreateDocoumentPicture(InlineShape inlineShape, bool isTridentLink)
        {
            DocumentPicture documentPicture = new DocumentPicture(inlineShape.AlternativeText);
            if (!documentPicture.HasGuid)
            {
                documentPicture.CreateGuid();
                inlineShape.AlternativeText = documentPicture.Key;
                if (isTridentLink)
                {
                    inlineShape.AlternativeText += "<TridentLink>Y</TridentLink>";
                    documentPicture.TridentLink = true;
                }
            }

            return documentPicture;
        }

        /// <summary>
        /// Creates the docoument picture.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <returns>DocumentPicture</returns>
        private static DocumentPicture CreateDocoumentPicture(Word.Shape shape, bool isTridentLink)
        {
            DocumentPicture documentPicture = new DocumentPicture(shape.AlternativeText);
            if (!documentPicture.HasGuid)
            {
                documentPicture.CreateGuid();
                shape.AlternativeText = documentPicture.Key;
            }

            return documentPicture;
        }

        /// <summary>
        /// Creates the new GUID XML.
        /// </summary>
        /// <returns>string</returns>
        private static string CreateNewGuidXml()
        {
            Guid guid = Guid.NewGuid();
            return string.Format("<Guid>{0}</Guid>", guid.ToString());
        }

        /// <summary>
        /// Inserts the appendix result.
        /// </summary>
        /// <param name="appendixResult">The appendix result.</param>
        private void InsertAppendixResult(AppendixResult appendixResult)
        {
            string alternativeText = "<AppendixObject>Y</AppendixObject>";
            if (appendixResult.IsImage)
            {
                InlineShape shape = this.Application.InsertPicture(appendixResult.ResultFilePaths[0]);
                shape.AlternativeText = CreateNewGuidXml() + alternativeText;
            }
            else
            {
                foreach (string resultFilePath in appendixResult.ResultFilePaths)
                {
                    InlineShape shape = this.Application.AddOleObject(resultFilePath);
                    shape.AlternativeText = CreateNewGuidXml() + alternativeText;
                }
            }
        }

        /// <summary>
        /// Inserts the parameter details.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        /// <param name="tableRange">The table range.</param>
        private void InsertParameterDetails(IList<AppendixParameter> parameters, Range tableRange, bool isInput)
        {
            string[][] headers = new string[2][];
            headers[0] = new string[] { "Input Parameter", "Value" };
            headers[1] = new string[] { "Output Parameter", "Value" };

            Table table = this.Application.CreateTable(isInput ? headers[0] : headers[1],
                                                       tableRange);
            foreach (AppendixParameter patameter in parameters)
            {
                table.AddRow(new string[] { patameter.Name, patameter.Value });
            }

            table.FormatTableHeader();
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public new void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Disposes the specified the privately held resources.
        /// </summary>
        /// <param name="disposing">if set to <c>true</c> disposes resources.</param>
        private void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources.
                if (disposing)
                {
                    this.CustomTaskPanes.Dispose();
                    this.taskPaneManager.Dispose();
                }
            }

            this.disposed = true;
        }

        #endregion
    }
}

